package com.cirnoworks.utils.file;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;

public class FileUtils {

	private static void tryWithResource(ResourceHandler rh) throws IOException {
		try {
			rh.run();
		} finally {
			rh.closeAllResources();
		}
	}

	public static int fillRead(InputStream is, byte[] buf) throws IOException {
		int read;
		int len = buf.length;
		int pos = 0;
		boolean got = false;
		while ((read = is.read(buf, pos, len - pos)) >= 0 && pos < len) {
			got = true;
			pos += read;
		}
		return got ? pos : -1;
	}

	public static boolean equals(File file1, File file2) throws IOException {
		if (file1.isFile() && file2.isFile()) {
			long len1 = file1.length();
			long len2 = file2.length();
			if (len1 > 0 && len2 > 0 && len1 != len2) {
				// lengths not equal
				return false;
			} else {
				// do the real compare
				final int BUF_SIZE = 16384;
				byte[] buf1 = new byte[BUF_SIZE];
				byte[] buf2 = new byte[BUF_SIZE];
				InputStream is1 = null;
				InputStream is2 = null;
				try {
					is1 = new FileInputStream(file1);
					is2 = new FileInputStream(file2);

					while (true) {
						int read1 = fillRead(is1, buf1);
						int read2 = fillRead(is2, buf2);
						if (read1 != read2) {
							return false;
						} else {
							if (read1 < 0) {
								return true;
							} else if (!Arrays.equals(buf1, buf2)) {
								return false;
							}
						}
					}
				} finally {
					try {
						if (is1 != null) {
							is1.close();
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
					try {
						if (is2 != null) {
							is2.close();
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} else {
			ArrayList<String> badFiles = new ArrayList<String>(2);
			if (!file1.isFile()) {
				badFiles.add(file1.getAbsolutePath());
			}
			if (!file2.isFile()) {
				badFiles.add(file2.getAbsolutePath());
			}
			if (badFiles.size() == 1) {
				throw new FileNotFoundException(badFiles.get(0)
						+ " not found or is not regular file");
			} else {
				throw new FileNotFoundException(badFiles.toString()
						+ " not found or is not regular file(s)");
			}
		}
	}

	public static void copyIfChanged(final File src, final File dest)
			throws IOException {
		if (!src.exists()) {
			throw new FileNotFoundException(src.getAbsolutePath());
		}
		if (dest.exists()) {
			if (!dest.isFile()) {
				throw new FileNotFoundException("Destination file "
						+ dest.getAbsolutePath()
						+ " is found and not a regular file");
			}
			if (equals(src, dest)) {
				System.out.println("  " + src.getAbsolutePath() + " => "
						+ dest.getAbsolutePath());
				return;
			}
		}
		System.out.println("m " + src.getAbsolutePath() + " => "
				+ dest.getAbsolutePath());
		tryWithResource(new ResourceHandler() {

			@Override
			protected void run() throws IOException {
				InputStream is = addResource(new FileInputStream(src));
				OutputStream os = addResource(new FileOutputStream(dest));
				byte[] buf = new byte[16384];
				int read;
				while ((read = is.read(buf)) >= 0) {
					os.write(buf, 0, read);
				}
			}
		});
	}

	public static void copyDirectory(final File src, final File dest,
			final FileFilter ff, final boolean verbose) throws IOException {
		if (!src.isDirectory()) {
			throw new IOException(src.getAbsolutePath() + " is not a directory");
		}
		if (dest.exists() && !dest.isDirectory()) {
			throw new FileNotFoundException("Destination directory "
					+ dest.getAbsolutePath() + " is not a directory");
		} else if (!dest.exists()) {
			dest.mkdirs();
		}
		File[] files = src.listFiles(ff);
		for (File file : files) {
			if (file.isDirectory()) {
				File distDir = new File(dest, file.getName());
				copyDirectory(file, distDir, ff, verbose);
			} else {
				File distFile = new File(dest, file.getName());
				copyIfChanged(file, distFile);
			}
		}
	}

	public static void move(File src, File dest) throws IOException {
		if (!src.exists()) {
			throw new FileNotFoundException(src.getAbsolutePath());
		}
		if (dest.exists()) {
			dest.delete();
		}
		if (!src.renameTo(dest)) {
			OutputStream os = null;
			InputStream is = null;
			try {
				os = new FileOutputStream(dest);
				is = new FileInputStream(src);
				byte[] buf = new byte[16384];
				int read;
				while ((read = is.read(buf)) >= 0) {
					os.write(buf, 0, read);
				}
			} finally {
				try {
					if (os != null)
						os.close();
				} catch (IOException e) {
				}
				try {
					if (is != null)
						is.close();
				} catch (IOException e) {
				}
			}
		}
	}

	public static void moveDirectory(final File src, final File dest,
			final FileFilter ff, final boolean verbose) throws IOException {
		if (!src.isDirectory()) {
			throw new IOException(src.getAbsolutePath() + " is not a directory");
		}
		File[] files = src.listFiles(ff);
		for (File file : files) {
			if (file.isDirectory()) {
				File distDir = new File(dest, file.getName());
				if (verbose) {
					System.out.println("d " + file.getAbsolutePath() + " => "
							+ distDir.getAbsolutePath());
				}
				distDir.mkdirs();
				moveDirectory(file, distDir, ff, verbose);
			} else {
				File distFile = new File(dest, file.getName());
				System.out.println("f " + file.getAbsolutePath() + " => "
						+ distFile.getAbsolutePath());
				move(file, distFile);
			}
		}
	}
}
